package xyz.jcat.sco.admin.oauth2.server.auth;


import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import xyz.jcat.common.util.ArrayUtils;
import xyz.jcat.common.util.StringUtils;
import xyz.jcat.sco.common.AuthConst;

import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CreateTokenService {

    private ClientDetailsService clientDetailsService;
    private OAuth2RequestFactory oAuth2RequestFactory;
    private AuthorizationServerTokenServices authorizationServerTokenServices;
    private PasswordEncoder passwordEncoder;
    private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();

    public OAuth2AccessToken createToken(HttpServletRequest request, Authentication authentication) {
        String authorization = request.getHeader(AuthConst.REQ_HEADER_AUTHORIZATION);
        if (authorization == null || !authorization.startsWith(AuthConst.REQ_HEADER_AUTHORIZATION_PREFIX)) {
            throw new UnapprovedClientAuthenticationException("Missing client info");
        }


        String[] clientInfo = decodeAuthorization(authorization);
        if(clientInfo.length != 2) {
            throw new UnapprovedClientAuthenticationException("Authorization error");
        }
        String clientId = clientInfo[0];
        String clientSecret = clientInfo[1];

        if(StringUtils.isBlank(clientId) || StringUtils.isBlank(clientSecret)) {
            throw new UnapprovedClientAuthenticationException("Missing client info");
        }

        Map<String, String> requestParameters = getRequestParameters(request);

        ClientDetails authenticatedClient = clientDetailsService.loadClientByClientId(clientId);

        if(!passwordEncoder.matches(clientSecret, authenticatedClient.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("Client secret error");
        }

        TokenRequest tokenRequest = oAuth2RequestFactory.createTokenRequest(requestParameters, authenticatedClient);

        oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);

        if (!org.springframework.util.StringUtils.hasText(tokenRequest.getGrantType())) {
            throw new InvalidRequestException("Missing grant type");
        }
        if (tokenRequest.getGrantType().equals("implicit")) {
            throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
        }

        if (!clientId.equals(tokenRequest.getClientId())) {
            throw new InvalidClientException("Given client ID does not match authenticated client");
        }

        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(authenticatedClient);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        return authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
    }

    public String[] decodeAuthorization(String authorization) {
        try {
            authorization = StringUtils.substringAfter(authorization, AuthConst.REQ_HEADER_AUTHORIZATION_PREFIX);
            String clientInfo = new String(Base64.getDecoder().decode(authorization));
            return clientInfo.split(":");
        }catch (Exception e) {
            throw new UnapprovedClientAuthenticationException("Authorization error");
        }
    }

    public Map<String, String> getRequestParameters(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Map<String, String[]> parameters = request.getParameterMap();
        parameters.forEach((p, vs) -> {
            if(ArrayUtils.isNotEmpty(vs)) {
                map.put(p, vs[0]);
            }
        });
        return map;
    }

    public CreateTokenService(ClientDetailsService clientDetailsService,
                              OAuth2RequestFactory oAuth2RequestFactory,
                              AuthorizationServerTokenServices authorizationServerTokenServices,
                              PasswordEncoder passwordEncoder) {
        this.clientDetailsService = clientDetailsService;
        this.oAuth2RequestFactory = oAuth2RequestFactory;
        this.authorizationServerTokenServices = authorizationServerTokenServices;
        this.passwordEncoder = passwordEncoder;
    }

}
